<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script>
    /*
    响应式原理：
    我们在使用的时候，获取属性，使用属性都是直接从vue实例获取的（app）
    设置属性值的时候，页面的数据更新
    响应，有回馈

    Object.defineProperty(对象，'设置什么属性名', {
      writeable
      configable
      set  赋值触发
      get  取值触发
      enumerable // 控制属性是否可枚举，是不是可以被for...in循环取值，但是依然可读可写可改
    })
    */
  
    let o1 = {};
    o1.name="张三";
    Object.defineProperty(o1, 'age', {
      configurable: true,
      writable: true,
      enumerable: true,
      value: 19
    })
  /*
  给o提供属性,,,,两种方式等价的
  get
  set
  要响应式就表示在赋值和读取的时候，附带做一些额外的事情,
  如果使用o.gender访问数据就会调用get（）
    使用o.xxx就会调用这个set方法，并且设置的值会作为参数传入set
    如果同时使用get和set需要中间变量存储真正的数据
    问题：这个_gender会暴露在全局作用于下，data中的每个属性都会存在全局下
    在vue中使用了defineRective（target,key,value,enumerable）
  */
 let _gender;
 Object.defineProperty(o1, 'gender', {
  configurable: true,
  enumerable: true,
  get() {
    return _gender;
  },
  set(newVal) {
    console.log('赋值的新值为'+newVal);
    _gender = newVal;
  }
 })
//  console.log(o1);
 
  </script>

  <script>
    /*
    在vue中使用了defineReactive（target,key,value,enumerable）
    但是一般对象会多级嵌套，
    */
   let o = {
     name:'zhang',
     age: 19,
     gender: 'www'
   }
   // 简化后版本,defineReactive函数的作用闭包
   function defineReactive(target, key, value, enumerable) {
    //  函数内部是一个局部作用域，这个value就只在函数内部使用的作用
    Object.defineProperty(target, key, {
      configurable: true,
      enumerable: !!enumerable,
      get() {
        console.log('读取o的'+key+'属性');
        return value
      },
      set(newVal) {
        console.log('设置o的'+key+'属性为：'+newVal);
        value = newVal
      }
    })
   }

   // 将对象转为响应式的
   let keys = Object.keys(o);
   for(let i =0;i<keys.length;i++) {
    defineReactive(o, keys[i], o[keys[i]], true)
   }
  </script>

  <script>
  function defineReactive2(target, key, value, enumerable) {
    //  函数内部是一个局部作用域，这个value就只在函数内部使用的作用

    /*
    判断是不是对象类型，是的话就继续递归
    */
    if(typeof value === 'object' && value != null && !Array.isArray(value)) {
      // 是非数组的引用类型
      reactify(value) ; // 递归
    }

    Object.defineProperty(target, key, {
      configurable: true,
      enumerable: !!enumerable,
      get() {
        console.log('读取o的'+key+'属性');
        return value
      },
      set(newVal) {
        console.log('设置o的'+key+'属性为：'+newVal);
        value = newVal
      }
    })
   }
    /*
    多级嵌套的响应式，数组，对象，多层，递归，
    */
   let data = {
     name: 'zs',
     age: 30,
     course: [
       {name: '语文'},
       {name: '数学'},
       {name: '英语'}
     ],
     user: {
       userInfo: {
         name: 'ls',
         age: 99
       }
     }
   }
   // 将对象o进行响应式化
   function reactify(o) {
    let keys = Object.keys(o)
    for(let i=0;i<keys.length;i++) {
      let key = keys[i]; // key属性名
      let value = o[key]
      /*
      判断这个属性是不是引用类型，判断是不是数据
      如果是引用类型就需要递归，否则就不用递归
      如果是数组？循环数组，将数组里边的元素进行响应式化
      不是引用类型，使用defineReactive2将其变成响应式
      是引用类型，也需要调用defineReactive2将其变成响应式
      */
     if(Array.isArray(value)) { // 数组
      for(let j=0;j<value.length;j++) {
        reactify(value[j]); // 递归
      }
     } else { // 对象或者值类型
       defineReactive2(o, key, value, true)
    }
  }
   }
   reactify(data)
  </script>

  <script>
    /*
    数组的一些方法实现响应式
    push，pop，shift, unshift, sort, splice
    1.在改变数据的时候发起通知，
      1.vue2的缺陷：数组发生变化，设置length没法实现
    2.加入的元素应该变成响应式的  

    技巧：如果一个函数已经定义了，但是我们需要扩展其功能，我们一般的处理办法：
    在函数原有的基础上增加额外的操作：函数的拦截
    1.使用一个临时的函数名存储函数
    2.重新定义原来的函数
    3.定义拓展的功能
    4.调用临时的那个函数
    */

    function func() {
      console.log('原始的功能');
    }
    // 1.打印出原始的功能  2.打印出新的拓展功能
    
    let _tmpFn = func; // 1
    func = function() { // 2
      _tmpFn() // 4
      console.log('新的拓展功能'); //3
    }
    // func()


    /*
    拓展数据的push和pop怎么处理？？？？
    修改要进行响应式化的数组的原型（__proto）
    push，pop，shift, unshift, sort, splice
    思路：原型式继承：修改原型链的结构
    let arr =[]
    继承关系： arr -> Array.prototype -> Object.prototype
    改写继承关系： arr -> arr.__proto__ -> Array.prototype -> Object.prototype
    
    __proto__兼容性问题
    vue源码中也做了判断：如果浏览器支持__proto__那么他就这么做，如果不支持，vue
    使用的是混入法
    */
    let ARRAY_METHOD = [
      "push", // push会返回数组的长度
      // "pop",
      "shift", 
      "unshift", 
      "sort", 
      "splice"
    ]
    let arr = [];// array_methods是一个空对象，原型继承自Array
    let array_methods = Object.create(Array.prototype);
    ARRAY_METHOD.forEach(method => {
      array_methods[method] = function() {
        console.log('调用的是拦截的'+method+'方法');
        /*
        将数据进行响应式化，数据为arguments
        */


        // 调用原来的方法
        // Array.prototype[method].call(this, ...arguments) ...只能用在真数组，会报错
        let res = Array.prototype[method].apply(this, arguments)
        return res; // 返回结果
      }
    })

    arr.__proto__ = array_methods;
  </script>
</body>
</html>